
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       mml_player.c
  * @author     baiyang
  * @date       2022-5-27
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "mml_player.h"

#include "hal_tone_alarm.h"
#include "notify.h"

#include <ctype.h>

#include <common/time/gp_time.h>
#include <common/gp_math/gp_mathlib.h>
/*-----------------------------------macro------------------------------------*/

/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/
static void prepare_to_play_string(mml_player_t mml, const char* string);
static void start_silence(mml_player_t mml, float duration);
static void start_note(mml_player_t mml, float duration, float frequency, float volume);
static char next_char(mml_player_t mml);
static uint8_t next_number(mml_player_t mml);
static size_t next_dots(mml_player_t mml);
static float rest_duration(mml_player_t mml, uint32_t rest_length, uint8_t dots);
static void next_action(mml_player_t mml);
/*----------------------------------variable----------------------------------*/

/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
void mmlplayer_update(mml_player_t mml)
{
    // Check if note is over
    if (mml->_playing && time_micros()-mml->_note_start_us > mml->_note_duration_us) {
        next_action(mml);
    }
}

void mmlplayer_play(mml_player_t mml, const char* string)
{
    prepare_to_play_string(mml, string);
    next_action(mml);
}

void mmlplayer_stop(mml_player_t mml)
{
    mml->_playing = false;
    hal_tonealarm_set_buzzer_tone(0,0,0);
}

static void prepare_to_play_string(mml_player_t mml, const char* string)
{
    mmlplayer_stop(mml);

    mml->_string = string;
    mml->_next = 0;
    mml->_tempo = 120;
    mml->_default_note_length = 4;
    mml->_note_mode = MML_MODE_NORMAL;
    mml->_octave = 4;
    mml->_volume = 255;
    mml->_silence_duration = 0;
    mml->_repeat = false;

    mml->_playing = true;
    mml->_note_duration_us = 0;
}

static void start_silence(mml_player_t mml, float duration)
{
    mml->_note_start_us = time_micros();
    mml->_note_duration_us = duration*1e6;
    hal_tonealarm_set_buzzer_tone(0, 0, 0);
}

static void start_note(mml_player_t mml, float duration, float frequency, float volume)
{
    mml->_note_start_us = time_micros();
    mml->_note_duration_us = duration*1e6;
    hal_tonealarm_set_buzzer_tone(frequency, volume, mml->_note_duration_us/1000U);
}

static char next_char(mml_player_t mml)
{
    while (mml->_string[mml->_next] != '\0' && isspace(mml->_string[mml->_next])) {
        mml->_next++;
    }

    return toupper(mml->_string[mml->_next]);
}

static uint8_t next_number(mml_player_t mml)
{
    uint8_t ret = 0;
    while (isdigit(next_char(mml))) {
        ret = (ret*10) + (next_char(mml) - '0');
        mml->_next++;
    }
    return ret;
}

static size_t next_dots(mml_player_t mml)
{
    size_t ret = 0;
    while (next_char(mml) == '.') {
        ret++;
        mml->_next++;
    }
    return ret;
}

static float rest_duration(mml_player_t mml, uint32_t rest_length, uint8_t dots)
{
    float whole_note_period = 240.0f / mml->_tempo;
    if (rest_length == 0) {
        rest_length = 1;
    }

    float rest_period = whole_note_period/rest_length;
    float dot_extension = rest_period * 0.5f;

    while (dots--) {
        rest_period += dot_extension;
        dot_extension *= 0.5f;
    }

    return rest_period;
}

static void next_action(mml_player_t mml)
{
    if (mml->_silence_duration > 0) {
        start_silence(mml, mml->_silence_duration);
        mml->_silence_duration = 0;
        return;
    }

    uint8_t note = 0;
    uint8_t note_length;

    while (note == 0) {
        char c = next_char(mml);
        if (c == '\0') {
            if (mml->_repeat) {
                // don't "play" here, as we may have been called from
                // there, and it turns out infinite recursion on
                // invalid strings is suboptimal.  The next call to
                // update() will push things out as appropriate.
                prepare_to_play_string(mml, mml->_string);
            } else {
                mmlplayer_stop(mml);
            }
            return;
        }

        mml->_next++;

        switch (c) {
        case 'V': {
            mml->_volume = next_number(mml);
            break;
        }
        case 'L': {
            mml->_default_note_length = next_number(mml);
            if (mml->_default_note_length == 0) {
                mmlplayer_stop(mml);
                return;
            }
            break;
        }
        case 'O':
            mml->_octave = next_number(mml);
            if (mml->_octave > 6) {
                mml->_octave = 6;
            }
            break;
        case '<':
            if (mml->_octave > 0) {
                mml->_octave--;
            }
            break;
        case '>':
            if (mml->_octave < 6) {
                mml->_octave++;
            }
            break;
        case 'M':
            c = next_char(mml);
            if (c == '\0') {
                mmlplayer_stop(mml);
                return;
            }
            mml->_next++;
            switch (c) {
            case 'N':
                mml->_note_mode = MML_MODE_NORMAL;
                break;
            case 'L':
                mml->_note_mode = MML_MODE_LEGATO;
                break;
            case 'S':
                mml->_note_mode = MML_MODE_STACCATO;
                break;
            case 'F':
                mml->_repeat = false;
                break;
            case 'B':
                mml->_repeat = true;
                break;
            default:
                mmlplayer_stop(mml);
                return;
            }
            break;
        case 'R':
        case 'P': {
            uint8_t num = next_number(mml);
            uint8_t dots = next_dots(mml);
            start_silence(mml, rest_duration(mml, num, dots));
            return;
        }
        case 'T':
            mml->_tempo = next_number(mml);
            if (mml->_tempo < 32) {
                mmlplayer_stop(mml);
                return;
            }
            break;
        case 'N':
            note = next_number(mml);
            note_length = mml->_default_note_length;
            if (note > 84) {
                mmlplayer_stop(mml);
                return;
            }
            if (note == 0) {
                uint8_t num = next_number(mml);
                uint8_t dots = next_dots(mml);
                start_silence(mml, rest_duration(mml, num, dots));
                return;
            }
            break;
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'G': {
            static const uint8_t note_tab[] = {9,11,0,2,4,5,7};
            note = note_tab[c-'A'] + (mml->_octave*12) + 1;

            c = next_char(mml);

            switch (c) {
            case '#':
            case '+':
                if (note < 84) {
                    note++;
                }
                mml->_next++;
                break;
            case '-':
                if (note > 1) {
                    note--;
                }
                mml->_next++;
                break;
            default:
                break;
            }
            note_length = next_number(mml);
            if (note_length == 0) {
                note_length = mml->_default_note_length;
            }
            break;
        }
        default:
            mmlplayer_stop(mml);
            return;
        }
    }

    // Avoid division by zero
    if (mml->_tempo == 0 || note_length == 0) {
        mmlplayer_stop(mml);
        return;
    }

    float note_period = 240.0f / (float)mml->_tempo / (float)note_length;

    switch (mml->_note_mode) {
    case MML_MODE_NORMAL:
        mml->_silence_duration = note_period/8;
        break;
    case MML_MODE_STACCATO:
        mml->_silence_duration = note_period/4;
        break;
    case MML_MODE_LEGATO:
        mml->_silence_duration = 0;
        break;
    }
    note_period -= mml->_silence_duration;

    float dot_extension = note_period * 0.5f;
    uint8_t dots = next_dots(mml);
    while (dots--) {
        note_period += dot_extension;
        dot_extension *= 0.5f;
    }

    float note_frequency = 880.0f * expf(logf(2.0f) * ((int)note - 46) / 12.0f);
    float note_volume = mml->_volume/255.0f;
    note_volume *= notify_get_buzz_volume() * 0.01;
    note_volume = math_constrain_float(note_volume, 0, 1);

    note_frequency = math_constrain_float(note_frequency, 10, 22000);

    start_note(mml, note_period, note_frequency, note_volume);
}

/*------------------------------------test------------------------------------*/


